package me.luger.core.mvc.interceptor.oplog;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.apache.bcel.classfile.MethodParameters;
import org.hibernate.validator.internal.engine.ValidatorImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
import org.springframework.web.servlet.support.RequestContextUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Valid;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by Administrator on 2016/8/15 0015.
 */
@Component
public class OpLogInterceptor implements HandlerInterceptor {
    protected final Log logger = LogFactory.getLog(getClass());

    private List<HandlerMethodArgumentResolver> argumentResolvers;

    private WebBindingInitializer webBindingInitializer;

    private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);
    private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<Class<?>, Set<Method>>(64);

    private static LocalVariableTableParameterNameDiscoverer parameterNameDiscovere = new LocalVariableTableParameterNameDiscoverer();
    private static Map<String, String[]> parameterNameCaches = new ConcurrentHashMap<String, String[]>();

    private static ExpressionParser parser = new SpelExpressionParser();
    //缓存SPEL Expression
    private static Map<String, Expression> spelExpressionCaches = new ConcurrentHashMap<String, Expression>();

    @Autowired
    public OpLogInterceptor(RequestMappingHandlerAdapter requestMappingHandlerAdapter){
        argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
        webBindingInitializer = requestMappingHandlerAdapter.getWebBindingInitializer();
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        HandlerMethod method = (HandlerMethod)handler;
        OpLog opLog = method.getMethodAnnotation(OpLog.class);
        if(opLog!=null){
            String msg = opLog.msg();
            MethodParameter[] parameters = method.getMethodParameters();
            Object[] parameterValues = new Object[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                MethodParameter parameter = parameters[i];
                HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
                ModelAndViewContainer mavContainer = new ModelAndViewContainer();
                mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
                WebDataBinderFactory webDataBinderFactory = getDataBinderFactory(method);
                Object value = resolver.resolveArgument(parameter, mavContainer, webRequest, webDataBinderFactory);
                parameterValues[i] = value;
            }

            String methodLongName = method.getMethod().getName();
            String[] parameterNames =  parameterNameCaches.get(methodLongName);
            if(parameterNames == null) {
                parameterNames = parameterNameDiscovere.getParameterNames(method.getMethod());
                parameterNameCaches.put(methodLongName, parameterNames);
            }

            EvaluationContext  context = new StandardEvaluationContext();
            if(parameterNames.length == parameterValues.length) {
                for(int i=0, len=parameterNames.length; i<len; i++)
                    context.setVariable(parameterNames[i], parameterValues[i]);
            }

            Expression expression = spelExpressionCaches.get(msg);
            if(expression == null) {
                expression = parser.parseExpression(msg, new TemplateParserContext());
                spelExpressionCaches.put(msg, expression);
            }


            String _msg = expression.getValue(context, String.class);
            System.out.println("_msg="+_msg);
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        HandlerMethod method = (HandlerMethod)handler;
        OpLog opLog = method.getMethodAnnotation(OpLog.class);
        if(opLog!=null){
            String msg = opLog.msg();
            MethodParameter[] parameters = method.getMethodParameters();
            Object[] parameterValues = new Object[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                MethodParameter parameter = parameters[i];
                HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
                ModelAndViewContainer mavContainer = new ModelAndViewContainer();
                mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
                WebDataBinderFactory webDataBinderFactory = getDataBinderFactory(method);
                Object value = resolver.resolveArgument(parameter, mavContainer, webRequest, webDataBinderFactory);
                parameterValues[i] = value;
            }

            String methodLongName = method.getMethod().getName();
            String[] parameterNames =  parameterNameCaches.get(methodLongName);
            if(parameterNames == null) {
                parameterNames = parameterNameDiscovere.getParameterNames(method.getMethod());
                parameterNameCaches.put(methodLongName, parameterNames);
            }

            EvaluationContext  context = new StandardEvaluationContext();
            if(parameterNames.length == parameterValues.length) {
                for(int i=0, len=parameterNames.length; i<len; i++)
                    context.setVariable(parameterNames[i], parameterValues[i]);
            }

            Expression expression = spelExpressionCaches.get(msg);
            if(expression == null) {
                expression = parser.parseExpression(msg, new TemplateParserContext());
                spelExpressionCaches.put(msg, expression);
            }


            String _msg = expression.getValue(context, String.class);
            System.out.println("_msg="+_msg);
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        HandlerMethod method = (HandlerMethod)handler;
        OpLog opLog = method.getMethodAnnotation(OpLog.class);
        if(opLog!=null){
            String msg = opLog.msg();
            MethodParameter[] parameters = method.getMethodParameters();
            Object[] parameterValues = new Object[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                MethodParameter parameter = parameters[i];
                HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
                ModelAndViewContainer mavContainer = new ModelAndViewContainer();
                mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
                WebDataBinderFactory webDataBinderFactory = getDataBinderFactory(method);
                Object value = resolver.resolveArgument(parameter, mavContainer, webRequest, webDataBinderFactory);
                parameterValues[i] = value;
            }


            String methodLongName = method.getMethod().getName();
            String[] parameterNames =  parameterNameCaches.get(methodLongName);
            if(parameterNames == null) {
                parameterNames = parameterNameDiscovere.getParameterNames(method.getMethod());
                parameterNameCaches.put(methodLongName, parameterNames);
            }

            EvaluationContext  context = new StandardEvaluationContext();
            if(parameterNames.length == parameterValues.length) {
                for(int i=0, len=parameterNames.length; i<len; i++)
                    context.setVariable(parameterNames[i], parameterValues[i]);
            }

            Expression expression = spelExpressionCaches.get(msg);
            if(expression == null) {
                expression = parser.parseExpression(msg, new TemplateParserContext());
                spelExpressionCaches.put(msg, expression);
            }


            String _msg = expression.getValue(context, String.class);
            System.out.println("_msg="+_msg);
        }

    }


    private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
        Class<?> handlerType = handlerMethod.getBeanType();
        Set<Method> methods = this.initBinderCache.get(handlerType);
        if (methods == null) {
            methods = HandlerMethodSelector.selectMethods(handlerType, RequestMappingHandlerAdapter.INIT_BINDER_METHODS);
            this.initBinderCache.put(handlerType, methods);
        }
        List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
        for (Method method : methods) {
            Object bean = handlerMethod.getBean();
            initBinderMethods.add(new InvocableHandlerMethod(bean, method));
        }
        return new ServletRequestDataBinderFactory(initBinderMethods, webBindingInitializer);
    }

    private HandlerMethodArgumentResolver getArgumentResolver(
            MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
                if (methodArgumentResolver.supportsParameter(parameter)) {
                    result = methodArgumentResolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }
}
